package com.phiture.erp.finance.core.handler;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.map.MapUtil;
import com.phiture.erp.basic.api.pojo.resp.ErpMaterialInfoResp;
import com.phiture.erp.common.enums.ErpAuditStatus;
import com.phiture.erp.finance.common.enums.ErpFinanceStatementTypeEnum;
import com.phiture.erp.finance.core.pojo.dto.ErpGenerateFinanceStatementDTO;
import com.phiture.erp.finance.core.pojo.dto.ErpRemoveFinanceStatementDTO;
import com.phiture.erp.finance.dal.entity.ErpFinanceStatementDO;
import com.phiture.erp.finance.dal.entity.ErpFinanceStatementItemsDO;
import com.phiture.erp.purchase.api.pojo.req.ErpPurchaseInReq;
import com.phiture.erp.purchase.api.pojo.req.ErpPurchaseReturnReq;
import com.phiture.erp.purchase.api.pojo.resp.ErpPurchaseInItemResp;
import com.phiture.erp.purchase.api.pojo.resp.ErpPurchaseInResp;
import com.phiture.erp.purchase.api.pojo.resp.ErpPurchaseReturnItemResp;
import com.phiture.erp.purchase.api.pojo.resp.ErpPurchaseReturnResp;
import com.phiture.erp.purchase.api.service.ErpPurchaseInApi;
import com.phiture.erp.purchase.api.service.ErpPurchaseReturnApi;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author YH
 * @date 2025-06-12-8:31
 * 付款单
 */

@Slf4j
@Component
@RequiredArgsConstructor
public class ErpFinanceSupplierHandler extends ErpBaseFinanceStatementHandler implements ErpFinanceStatementHandler {
    private final ErpPurchaseInApi erpPurchaseInApi;
    private final ErpPurchaseReturnApi erpPurchaseReturnApi;

    @Override
    public boolean generate(ErpGenerateFinanceStatementDTO dto) {
        Pair<LocalDate, LocalDate> datePair = periodConvert(dto.getBillingPeriod());
        LocalDate startDate = datePair.getKey(), endDate = datePair.getValue();
        List<ErpPurchaseInResp> purchaseInList = getPurchaseInList(startDate, endDate, ErpAuditStatus.APPROVE.getStatus());
        List<ErpPurchaseReturnResp> purchaseReturnList = getPurchaseReturnList(startDate, endDate, ErpAuditStatus.APPROVE.getStatus());
        if(CollUtil.isEmpty(purchaseInList) && CollUtil.isEmpty(purchaseReturnList)){
            log.warn("[ErpFinancePaymentHandler.generate] 生成供应商对账单失败，采购入库单和采购退货单为空");
            return false;
        }
        List<ErpFinanceStatementDO> statementList = buildErpFinanceStatementDOList(dto, purchaseInList, purchaseReturnList);
        Map<Long, javafx.util.Pair<String, Long>> purchaseInMap = purchaseInList.stream().collect(Collectors.toMap(ErpPurchaseInResp::getPurchaseInId, purchaseIn -> new javafx.util.Pair<>(purchaseIn.getPurchaseInNo(), purchaseIn.getSupplierId())));
        Map<Long, javafx.util.Pair<String, Long>> purchaseReturnMap = purchaseReturnList.stream().collect(Collectors.toMap(ErpPurchaseReturnResp::getPurchaseReturnId, purchaseReturn -> new javafx.util.Pair<>(purchaseReturn.getPurchaseReturnNo(), purchaseReturn.getSupplierId())));
        List<ErpFinanceStatementItemsDO> statementItemsList = buildErpFinanceStatementItemsDO(purchaseInMap, purchaseReturnMap);
        return Boolean.TRUE.equals(transactionTemplate.execute(action -> {
            List<ErpFinanceStatementDO> saveStatementList = erpFinanceStatementService.batchSaveFinanceStatement(statementList);
            Map<Long, Long> statementMap = saveStatementList.stream().collect(Collectors.toMap(ErpFinanceStatementDO::getTransactionUserId, ErpFinanceStatementDO::getId));
            if (CollUtil.isNotEmpty(statementItemsList)) {
                statementItemsList.forEach(item -> item.setStatementId(statementMap.get(item.getTransactionUserId())));
                erpFinanceStatementService.batchSaveFinanceStatementItem(statementItemsList);
            }
            erpPurchaseInApi.backfillPurchaseInAccountCheckFlag(purchaseInMap.keySet(), Boolean.TRUE);
            erpPurchaseReturnApi.backfillPurchaseReturnAccountCheckFlag(purchaseReturnMap.keySet(), Boolean.TRUE);
            return true;
        }));
    }

    @Override
    public void remove(ErpRemoveFinanceStatementDTO dto) {
        long id = dto.getFinanceStatementId();
        checkFinanceStatementById(id);
        List<ErpFinanceStatementItemsDO> items = getItemsByFinanceStatementId(id);
        Set<String> noSet = new HashSet<>(items.size());
        List<Long> itemIdList = new ArrayList<>(items.size());
        items.forEach(item ->{
            noSet.add(item.getSourceNo());
            itemIdList.add(item.getId());
        });

        transactionTemplate.execute(action ->{
            erpPurchaseInApi.updatePurchaseInAccountCheckFlag(noSet, Boolean.FALSE);
            erpPurchaseReturnApi.updatePurchaseReturnAccountCheckFlag(noSet, Boolean.FALSE);
            removeFinanceStatementById(id);
            removeFinanceStatementItemByItemIds(itemIdList);
            return true;
        });
    }

    @Override
    public String getStrategyType() {
        return ErpFinanceStatementTypeEnum.SUPPLIER_PAYMENT.getVal();
    }


    private List<ErpFinanceStatementDO> buildErpFinanceStatementDOList(ErpGenerateFinanceStatementDTO dto, List<ErpPurchaseInResp> purchaseInList, List<ErpPurchaseReturnResp> purchaseReturnList) {
        Map<Long, ErpFinanceStatementDO> financeStatementMap = new HashMap<>(purchaseInList.size() + purchaseReturnList.size());
        if (CollUtil.isNotEmpty(purchaseInList)) {
            purchaseInList.forEach(
                    purchaseIn -> financeStatementMap.merge(
                            purchaseIn.getSupplierId(),
                            toErpFinanceStatementDO(purchaseIn.getSupplierId(), dto.getType(), dto.getBillingPeriod(), purchaseIn.getTotalPrice()),
                            (originalValue, newValue) -> originalValue.setTotalAmount(originalValue.getTotalAmount().add(newValue.getTotalAmount()))
                    )
            );
        }

        if (CollUtil.isNotEmpty(purchaseReturnList)) {
            purchaseReturnList.forEach(
                    purchaseReturn -> financeStatementMap.merge(
                            purchaseReturn.getSupplierId(),
                            toErpFinanceStatementDO(purchaseReturn.getSupplierId(), dto.getType(), dto.getBillingPeriod(), purchaseReturn.getTotalPrice().negate()),
                            (originalValue, newValue) -> originalValue.setTotalAmount(originalValue.getTotalAmount().add(newValue.getTotalAmount()))
                    )
            );
        }
        return MapUtil.isEmpty(financeStatementMap) ? Collections.emptyList() : new ArrayList<>(financeStatementMap.values());
    }

    private List<ErpFinanceStatementItemsDO> buildErpFinanceStatementItemsDO(Map<Long, javafx.util.Pair<String, Long>> purchaseInMap, Map<Long, javafx.util.Pair<String, Long>> purchaseReturnMap){
        List<ErpPurchaseInItemResp> purchaseInItemList = getPurchaseInItemList(purchaseInMap);
        List<ErpPurchaseReturnItemResp> purchaseReturnItemList = getPurchaseReturnItemList(purchaseReturnMap);
        if(CollUtil.isEmpty(purchaseInItemList) && CollUtil.isEmpty(purchaseReturnItemList)){
            return Collections.emptyList();
        }

        Set<Long> materialIds = Stream.concat(
                        purchaseInItemList.stream().map(ErpPurchaseInItemResp::getMaterialId),
                        purchaseReturnItemList.stream().map(ErpPurchaseReturnItemResp::getMaterialId)
                ).collect(Collectors.toSet());
        Map<Long, ErpMaterialInfoResp> materialMap = getMaterialMap(materialIds);

        List<ErpFinanceStatementItemsDO> statementItems = new ArrayList<>(purchaseInMap.size() + purchaseReturnMap.size());
        if(CollUtil.isNotEmpty(purchaseInItemList)){
            purchaseInItemList.forEach(item -> statementItems.add(
                    toErpFinanceStatementItemsDO(
                            item.getPurchaseInId(), item.getPurchaseInNo(), item.getPurchaseInItemId(), item.getSupplierId(),
                            item.getCount(), item.getMaterialPrice(), item.getTaxPercent(),
                            convertPrice(item.getTaxPrice()), convertPrice(item.getTotalPrice()), materialMap.get(item.getMaterialId())
                    )
            ));
        }

        if(CollUtil.isNotEmpty(purchaseReturnItemList)){
            purchaseReturnItemList.forEach(item -> statementItems.add(
                    toErpFinanceStatementItemsDO(
                            item.getPurchaseReturnId(), item.getPurchaseReturnNo(), item.getPurchaseReturnItemId(), item.getSupplierId(),
                            item.getCount().negate(), item.getMaterialPrice(), item.getTaxPercent(),
                            convertPrice(item.getTaxPrice()).negate(), convertPrice(item.getTotalPrice()).negate(), materialMap.get(item.getMaterialId())
                    )
            ));
        }
        return statementItems;
    }

    private List<ErpPurchaseInResp> getPurchaseInList(LocalDate startDate, LocalDate endDate, Integer status) {
        ErpPurchaseInReq req = new ErpPurchaseInReq();
        req.setStartDate(startDate);
        req.setEndDate(endDate);
        req.setStatus(status);
        return erpPurchaseInApi.getPurchaseInList(req);
    }

    private List<ErpPurchaseInItemResp> getPurchaseInItemList(Map<Long, javafx.util.Pair<String, Long>> purchaseInMap) {
        return MapUtil.isEmpty(purchaseInMap) ? Collections.emptyList() : erpPurchaseInApi.getPurchaseInItemList(purchaseInMap);
    }

    private List<ErpPurchaseReturnResp> getPurchaseReturnList(LocalDate startDate, LocalDate endDate, Integer status) {
        ErpPurchaseReturnReq req = new ErpPurchaseReturnReq();
        req.setStartDate(startDate);
        req.setEndDate(endDate);
        req.setStatus(status);
        return erpPurchaseReturnApi.getPurchaseReturnList(req);
    }

    private List<ErpPurchaseReturnItemResp> getPurchaseReturnItemList(Map<Long, javafx.util.Pair<String, Long>> purchaseReturnMap) {
        return MapUtil.isEmpty(purchaseReturnMap) ? Collections.emptyList() : erpPurchaseReturnApi.getPurchaseReturnItemList(purchaseReturnMap);
    }
}
